package com.rongxun.utils.wallet;

import com.rongxun.hbwallet.constants.HttpConstants;
import com.rongxun.hbwallet.constants.SPKey;
import com.rongxun.utils.FlyLog;
import com.rongxun.utils.PreferenceUtils;

import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.FunctionReturnDecoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Bool;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.abi.datatypes.generated.Uint8;
import org.web3j.crypto.CipherException;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.crypto.WalletUtils;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.admin.Admin;
import org.web3j.protocol.admin.AdminFactory;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.request.Transaction;
import org.web3j.protocol.core.methods.response.EthCall;
import org.web3j.protocol.core.methods.response.EthEstimateGas;
import org.web3j.protocol.core.methods.response.EthGasPrice;
import org.web3j.protocol.core.methods.response.EthGetBalance;
import org.web3j.protocol.core.methods.response.EthGetTransactionCount;
import org.web3j.protocol.core.methods.response.EthSendTransaction;
import org.web3j.protocol.http.HttpService;
import org.web3j.tx.ChainId;
import org.web3j.utils.Convert;
import org.web3j.utils.Numeric;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;

/**
 * 创建者     庄丰泽
 * 创建时间   2018/8/17 17:19
 * 描述	      交易相关
 */

public class TransactionUtils {

    private static String tokenAddress = HttpConstants.Token.TOKEN_ADDRESS;//代币地址
    private static String tokenUrl     = HttpConstants.Token.TOKEN_URL;//节点地址

    public static BigInteger getTransactionGasPrice() {
        BigInteger gasPrice = new BigInteger("11000");

        Admin admin = AdminFactory.build(new HttpService(tokenUrl));
        try {
            EthGasPrice send = admin.ethGasPrice().send();
            gasPrice = send.getGasPrice();
            FlyLog.i(gasPrice + "");

        } catch (IOException e) {
            e.printStackTrace();
        }
        return gasPrice;
    }

    /**
     * 获取代币余额
     */
    public static BigInteger getTokenBalance(String fromAddress) {

        String methodName = "balanceOf";

        Admin web3j = AdminFactory.build(new HttpService(tokenUrl));

        List<Type> inputParameters = new ArrayList<>();
        List<TypeReference<?>> outputParameters = new ArrayList<>();
        Address addresss = new Address(fromAddress);
        inputParameters.add(addresss);
        TypeReference<Uint256> typeReference = new TypeReference<Uint256>() {
        };
        outputParameters.add(typeReference);

        Function function = new Function(methodName, inputParameters, outputParameters);
        String data = FunctionEncoder.encode(function);

        Transaction transaction = Transaction.createEthCallTransaction(fromAddress, tokenAddress, data);
        EthCall ethCall;
        BigInteger balanceValue = BigInteger.ZERO;
        try {
            ethCall = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).send();

            String value = ethCall.getValue();

            List<Type> results = FunctionReturnDecoder.decode(value, function.getOutputParameters());


            balanceValue = (BigInteger) results.get(0).getValue();


        } catch (IOException e) {
            e.printStackTrace();
        }

        int tokenDecimals = getTokenDecimals(web3j, fromAddress);

        return balanceValue.divide(BigInteger.TEN.pow(tokenDecimals));
    }


    /**
     * 获取以太币余额
     */
    public static BigDecimal getEthBalance(String fromAddress) {
        Admin web3j = AdminFactory.build(new HttpService(tokenUrl));
        BigInteger balance = null;
        try {
            EthGetBalance send = web3j.ethGetBalance(fromAddress, DefaultBlockParameterName.LATEST).send();

            balance = send.getBalance();

        } catch (IOException e) {
            e.printStackTrace();
        }
        FlyLog.i("ethBalance " + balance);
        return Convert.fromWei(balance + "", Convert.Unit.ETHER);

    }


    /**
     * 查询代币精度
     *
     * @param web3j
     * @return
     */
    public static int getTokenDecimals(Web3j web3j, String fromAddress) {
        String methodName = "decimals";
        String fromAddr = fromAddress;
        int decimal = 0;
        List<Type> inputParameters = new ArrayList<>();
        List<TypeReference<?>> outputParameters = new ArrayList<>();

        TypeReference<Uint8> typeReference = new TypeReference<Uint8>() {
        };
        outputParameters.add(typeReference);

        Function function = new Function(methodName, inputParameters, outputParameters);

        String data = FunctionEncoder.encode(function);
        Transaction transaction = Transaction.createEthCallTransaction(fromAddr, tokenAddress, data);

        EthCall ethCall;
        try {
            ethCall = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).sendAsync().get();
            List<Type> results = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters());
            decimal = Integer.parseInt(results.get(0).getValue().toString());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        return decimal;
    }

    private static Credentials getCredentials(String password) {
        String userId = PreferenceUtils.getString(SPKey.USERID, "0");
        ETHWallet ethWallet = WalletDaoUtils.checkRepeatByUserId(userId);

        Credentials credentials = null;
        //转账人私钥
        try {
            credentials = WalletUtils.loadCredentials(
                    password,
                    ethWallet.getKeystorePath());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (CipherException e) {
            e.printStackTrace();
        }
        return credentials;
    }


    /**
     * 发送一个普通交易
     *
     * @return 交易 Hash
     */
    public static String sendTransaction(String fromAddress, String password, String toAddress, BigInteger gasPrice, BigInteger gasLimit, BigDecimal amount) {
        Admin admin = AdminFactory.build(new HttpService(tokenUrl));

        String txHash = null;

        try {
            Credentials credentials = getCredentials(password);

            //getNonce
            BigInteger nonce = admin.ethGetTransactionCount(fromAddress, DefaultBlockParameterName.LATEST).sendAsync().get().getTransactionCount();


            //创建交易
            BigInteger value = Convert.toWei(amount, Convert.Unit.ETHER).toBigInteger();


            RawTransaction rawTransaction = RawTransaction.createEtherTransaction(
                    nonce, gasPrice, gasLimit, toAddress, value);

            //对交易做签名
            byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
            String hexValue = Numeric.toHexString(signedMessage);

            //发送交易
            EthSendTransaction ethSendTransaction =
                    admin.ethSendRawTransaction(hexValue).sendAsync().get();

            //获取交易结果
            txHash = ethSendTransaction.getTransactionHash();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return txHash;
    }


    //    /**
    //     * 代币转账
    //     */
    //    public static String sendTokenTransaction(String fromAddress, String password, String toAddress, BigInteger amount, BigInteger gasLimit, BigInteger gasPrice) {
    //
    //        String txHash = null;
    //        try {
    //            //地址就可以了
    //            Admin admin = AdminFactory.build(new HttpService(tokenUrl));
    ////            admin.personalUnlockAccount(fromAddress,password);
    //            //获取NONCE
    //
    //            BigInteger nonce = admin.ethGetTransactionCount(
    //                    fromAddress, DefaultBlockParameterName.PENDING).sendAsync().get().getTransactionCount();
    //
    //            FlyLog.i("sendTokenTransaction: nonce =" + nonce);
    //
    //            String methodName = "transfer";
    //
    //            List<Type> inputParameters = new ArrayList<>();
    //            List<TypeReference<?>> outputParameters = new ArrayList<>();
    //
    //            Address tAddress = new Address(toAddress);
    //
    //            int tokenDecimals = getTokenDecimals(admin, fromAddress);
    //
    //
    //            Uint256 value = new Uint256(amount.multiply(BigInteger.TEN.pow(tokenDecimals)));
    //            FlyLog.i(tAddress.toString() + "");
    //
    //            inputParameters.add(tAddress);
    //            inputParameters.add(value);
    //
    //            TypeReference<Bool> typeReference = new TypeReference<Bool>() {
    //            };
    //            outputParameters.add(typeReference);
    //
    //            Function function = new Function(methodName, inputParameters, outputParameters);
    //
    //            String encodedFunction = FunctionEncoder.encode(function);
    //            FlyLog.i("sendTokenTransaction: encodedFunction =" + encodedFunction);
    //
    //            //智能合约事物
    //            RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, toAddress, encodedFunction);
    //            //私钥获取凭证
    //            Credentials credentials = getCredentials(password);
    //
    //            //签名
    //            byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
    //            String hexValue = Numeric.toHexString(signedMessage);
    //
    //            FlyLog.i("sendTokenTransaction: hexValue =" + hexValue);
    //
    //            //发送事务
    //            EthSendTransaction ethSendTransaction = admin.ethSendRawTransaction(hexValue).send();
    //            //事物的HASH
    //            txHash = ethSendTransaction.getTransactionHash();
    //
    //        } catch (InterruptedException e) {
    //            FlyLog.i("sendTokenTransaction InterruptedException");
    //            e.printStackTrace();
    //        } catch (ExecutionException e) {
    //
    //            FlyLog.i("sendTokenTransaction ExecutionException");
    //            e.printStackTrace();
    //        } catch (IOException e) {
    //            FlyLog.i("sendTokenTransaction IOException");
    //            e.printStackTrace();
    //        }
    //
    //        return txHash;
    //    }

    /**
     * 生成一个普通交易对象
     *
     * @param fromAddress 放款方
     * @param toAddress   收款方
     * @param nonce       交易序号
     * @param gasPrice    gas 价格
     * @param gasLimit    gas 数量
     * @param value       金额
     * @return 交易对象
     */
    public static Transaction makeTransaction(String fromAddress, String toAddress,
                                              BigInteger nonce, BigInteger gasPrice,
                                              BigInteger gasLimit, BigInteger value) {
        Transaction transaction = Transaction.createEtherTransaction(fromAddress, nonce, gasPrice, gasLimit, toAddress, value);
        return transaction;
    }

    /**
     * 获取普通交易的gas上限
     *
     * @param transaction 交易对象
     * @return gas 上限
     */
    public static BigInteger getTransactionGasLimit(Transaction transaction) {
        BigInteger gasLimit = new BigInteger("21000");

        Admin admin = AdminFactory.build(new HttpService(tokenUrl));

        try {
            EthEstimateGas ethEstimateGas = admin.ethEstimateGas(transaction).send();
            gasLimit = ethEstimateGas.getAmountUsed();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return gasLimit;
    }


    /**
     * 获取账号交易次数 nonce
     *
     * @param fromAddress 钱包地址
     * @return nonce
     */
    public static BigInteger getTransactionNonce(String fromAddress) {
        BigInteger nonce = BigInteger.ZERO;
        Admin admin = AdminFactory.build(new HttpService(tokenUrl));
        try {
            EthGetTransactionCount ethGetTransactionCount = admin.ethGetTransactionCount(fromAddress, DefaultBlockParameterName.PENDING).send();
            nonce = ethGetTransactionCount.getTransactionCount();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return nonce;
    }

    public static String sendTokenTransaction(String fromAddress, String password, String toAddress, BigInteger amount, BigInteger gasPrice, BigInteger gasLimit) {
        BigInteger nonce;

        Admin admin = AdminFactory.build(new HttpService(tokenUrl));

        EthGetTransactionCount ethGetTransactionCount = null;
        try {
            ethGetTransactionCount = admin.ethGetTransactionCount(fromAddress, DefaultBlockParameterName.PENDING).send();
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (ethGetTransactionCount == null) {
            return null;
        }
        nonce = ethGetTransactionCount.getTransactionCount();
        int tokenDecimals = getTokenDecimals(admin, fromAddress);

        FlyLog.i("nonce " + nonce);
        BigInteger value = BigInteger.ZERO;
        //token转账参数
        String methodName = "transfer";
        List<Type> inputParameters = new ArrayList<>();
        List<TypeReference<?>> outputParameters = new ArrayList<>();
        Address tAddress = new Address(toAddress);

        Uint256 tokenValue = new Uint256(amount.multiply(BigInteger.TEN.pow(tokenDecimals)));
        FlyLog.i("tokenValue:" + tokenValue);
        inputParameters.add(tAddress);
        inputParameters.add(tokenValue);
        TypeReference<Bool> typeReference = new TypeReference<Bool>() {
        };
        outputParameters.add(typeReference);
        Function function = new Function(methodName, inputParameters, outputParameters);
        String data = FunctionEncoder.encode(function);

        String signedData = null;
        String txhash = null;
        //        try {
        //            FlyLog.i("gasLimit"+gasLimit);
        //            FlyLog.i("gasPrice"+gasPrice);
        //
        //            signedData = signContractTransaction(toAddress, nonce, gasPrice, gasLimit, amount, tokenDecimals, password);
        //            FlyLog.i("signedData:" + signedData);
        //
        //
        //            EthSendTransaction send = admin.ethSendRawTransaction(signedData).send();
        //
        //
        //            if (send.hasError()) {
        //                int code = send.getError().getCode();
        //                String data = send.getError().getData();
        //                String message = send.getError().getMessage();
        //                FlyLog.i("code :" + code);
        //                FlyLog.i("data :" + data);
        //                FlyLog.i("message :" + message);
        //            }
        //            txhash = send.getTransactionHash();
        //
        //
        //        }/* catch (InterruptedException e) {
        //            e.printStackTrace();
        //        } catch (ExecutionException e) {
        //            e.printStackTrace();
        //        }*/ catch (IOException e) {
        //            e.printStackTrace();
        //        } catch (CipherException e) {
        //            e.printStackTrace();
        //        }


        try {
            signedData = signTransaction(nonce, gasPrice, gasLimit, tokenAddress, value, data, password);

            FlyLog.i("signedData:" + signedData);

            if (signedData != null) {
                EthSendTransaction ethSendTransaction = admin.ethSendRawTransaction(signedData).send();
                txhash = ethSendTransaction.getTransactionHash();

                if (ethSendTransaction.hasError()) {
                    int code = ethSendTransaction.getError().getCode();
                    String errordata = ethSendTransaction.getError().getData();
                    String message = ethSendTransaction.getError().getMessage();
                    FlyLog.i("code :" + code);
                    FlyLog.i("errordata :" + errordata);
                    FlyLog.i("message :" + message);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        FlyLog.i("txHash" + txhash);

        return txhash;
    }

    private static byte chainId = ChainId.MAINNET;

    /**
     * 签名交易
     */

    public static String signTransaction(BigInteger nonce, BigInteger gasPrice, BigInteger gasLimit, String to,
                                         BigInteger value, String data, String password) throws IOException {
        byte[] signedMessage;
        RawTransaction rawTransaction = RawTransaction.createTransaction(
                nonce,
                gasPrice,
                gasLimit,
                to,
                value,
                data);

        //        RawTransaction contractTransaction = RawTransaction.createContractTransaction(nonce,
        //                gasPrice,
        //                gasLimit,
        //                value, data);
        if (chainId > ChainId.NONE) {
            signedMessage = TransactionEncoder.signMessage(rawTransaction, chainId, getCredentials(password));
        } else {
            signedMessage = TransactionEncoder.signMessage(rawTransaction, getCredentials(password));
        }

        String hexValue = Numeric.toHexString(signedMessage);

        return hexValue;
    }

    public static String signContractTransaction(String to,
                                                 BigInteger nonce,
                                                 BigInteger gasPrice,
                                                 BigInteger gasLimit,
                                                 BigInteger amount,
                                                 int decimal,
                                                 String password) throws IOException, CipherException {

        Uint256 tokenValue = new Uint256(amount.multiply(BigInteger.TEN.pow(decimal)));

        Function function = new Function("transfer",

                Arrays.asList(new Address(to), tokenValue),

                Collections.emptyList());

        String data = FunctionEncoder.encode(function);

        RawTransaction rawTransaction = RawTransaction.createTransaction(

                nonce,

                gasPrice,

                gasLimit,

                tokenAddress,

                data);

        return signData(rawTransaction, password);

    }

    private static String signData(RawTransaction rawTransaction, String password) {

        Credentials credentials = getCredentials(password);

        byte[] signMessage = TransactionEncoder.signMessage(rawTransaction, ChainId.NONE, credentials);

        return Numeric.toHexString(signMessage);

    }
}
